<!DOCTYPE html>
<html lang="zh_CN" class="tip">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>电子琴</title>
  <style>
    html {

      font-size: calc(100vmin / 7.5);
      background-color: #efefef;
    }



    div {
      box-sizing: border-box;
      transition: all .1s;
      user-select: none;
    }

    html:not(.tip) .white,
    html:not(.tip) .black,
    html:not(.tip) .index div,
    html:not(.tip) .space .text {
      color: transparent;
    }

    .tip .i-tip {
      color: cornflowerblue;
    }


    .space {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      height: 5vh;
      background: #fff;
      z-index: 10;
      border-bottom: .01rem solid #ccc;
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: .24rem;
      padding: 0 .16rem;
    }

    .space.active {
      background-color: #000;
      color: white;
    }

    .space .i-help,
    .space .i-tip {
      width: .24rem;
      height: .24rem;
    }


    .keys {
      display: flex;
      position: fixed;
      top: 5vh;
      height: 90vh;
      left: 50%;
      transform: translate(-50%, 0%);

    }



    .black-keys {
      position: absolute;
      left: 0;
      right: 0;
      display: flex;
      justify-content: center;
      height: 50%;
      /* 49.44 */
    }

    .white,
    .black {
      border-radius: 0 0 .12rem .12rem;
      transform-origin: 0 0;
      font-size: .32rem;
      color: #666;
      display: flex;
      justify-content: center;
      align-items: flex-end;
      padding-bottom: .32rem;
    }



    .white {
      width: 12vw;
      height: 80%;
      background-color: #fff;
      border: .01rem solid #ccc;

    }

    .black {
      width: 9vw;
      background-color: #000;
      margin: 0 1.5vw;
    }

    .black:nth-of-type(3) {
      visibility: hidden;
    }

    .white.active,
    .black.active {
      transform: rotateX(15deg);
    }

    .index {
      position: fixed;
      bottom: 0;
      right: 0;
      left: 0;
      display: flex;
      justify-content: space-between;
      align-items: flex-end;

    }

    .index>div {
      flex: 1 1 auto;
      border: .01rem solid #ccc;
      transform-origin: bottom;
      height: 5vh;
      text-align: center;
      display: flex;
      justify-content: center;
      align-items: center;
      color: white;
      font-size: .18rem;

      /* margin: 0 0.5rem; */
    }

    .index>div.active {
      height: 3vh;
    }

    .index>div:nth-of-type(1) {
      background: #bbb;
    }

    .index>div:nth-of-type(2) {
      background: #aaa;
    }

    .index>div:nth-of-type(3) {
      background: #999;
    }

    .index>div:nth-of-type(4) {
      background: #888;
    }

    .index>div:nth-of-type(5) {
      background: #777;
    }

    .index>div:nth-of-type(6) {
      background: #666;
    }

    .index>div:nth-of-type(7) {
      background: #555;
    }

    .index>div:nth-of-type(8) {
      background: #444;
    }

    .index>div:nth-of-type(9) {
      background: #333;
    }

    .loading {
      background-color: rgba(255, 255, 255, 0.8);
      position: fixed;
      top: 0;
      right: 0;
      left: 0;
      bottom: 0;
      z-index: 100;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-flow: column;
      font-size: .32rem;
      color: #666;
      transition: opacity 1s .5s;
    }

    .loading.hide {
      opacity: 0;
    }

    .loading.hided {
      display: none;
    }

    .loading .i-loading {
      color: cornflowerblue;
      width: 1.28rem;
      height: 1.28rem;
      margin-bottom: .24rem;
    }
  </style>
</head>

<body>

  <div class="loading">
    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"
      class="i-loading">
      <circle cx="12" cy="2" r="0" fill="currentColor">
        <animate attributeName="r" begin="0" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(45 12 12)">
        <animate attributeName="r" begin="0.125s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(90 12 12)">
        <animate attributeName="r" begin="0.25s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(135 12 12)">
        <animate attributeName="r" begin="0.375s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(180 12 12)">
        <animate attributeName="r" begin="0.5s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(225 12 12)">
        <animate attributeName="r" begin="0.625s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(270 12 12)">
        <animate attributeName="r" begin="0.75s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
      <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(315 12 12)">
        <animate attributeName="r" begin="0.875s" calcMode="spline" dur="1s"
          keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
          values="0;2;0;0" />
      </circle>
    </svg>
    <div class="text">资源加载中……</div>
  </div>

  <div class="space">
    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" class="i-tip"
      onclick="handleTipClick(event)">
      <path fill="currentColor"
        d="m22 10l-.625-1.375L20 8l1.375-.625L22 6l.625 1.375L24 8l-1.375.625Zm-3-4l-.95-2.05L16 3l2.05-.95L19 0l.95 2.05L22 3l-2.05.95ZM9 22q-.825 0-1.412-.587Q7 20.825 7 20h4q0 .825-.587 1.413Q9.825 22 9 22Zm-4-3v-2h8v2Zm.25-3q-1.725-1.025-2.737-2.75Q1.5 11.525 1.5 9.5q0-3.125 2.188-5.312Q5.875 2 9 2q3.125 0 5.312 2.188Q16.5 6.375 16.5 9.5q0 2.025-1.012 3.75q-1.013 1.725-2.738 2.75Zm.6-2h6.3q1.125-.8 1.737-1.975q.613-1.175.613-2.525q0-2.3-1.6-3.9T9 4Q6.7 4 5.1 5.6T3.5 9.5q0 1.35.613 2.525Q4.725 13.2 5.85 14ZM9 14Z" />
    </svg>
    <div class="text">SPACE</div>
    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"
      class="i-help" title="帮助" onclick="handleHelpClick(event)">
      <path fill="currentColor"
        d="M11 18h2v-2h-2v2m1-16A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m0-14a4 4 0 0 0-4 4h2a2 2 0 0 1 2-2a2 2 0 0 1 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5a4 4 0 0 0-4-4Z" />
    </svg>
  </div>

  <div class="keys">
    <div class="white">1</div>
    <div class="white">2</div>
    <div class="white">3</div>
    <div class="white">4</div>
    <div class="white">5</div>
    <div class="white">6</div>
    <div class="white">7</div>
    <div class="black-keys">
      <div class="black">#1</div>
      <div class="black">#2</div>
      <div class="black"></div>
      <div class="black">#4</div>
      <div class="black">#5</div>
      <div class="black">#6</div>
    </div>
  </div>

  <div class="index">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div class="active">5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
  </div>

  <script>
    const handleTipClick = ev => {
      document.documentElement.classList.toggle("tip");
    }
    const handleHelpClick = ev => {
      location.href = "./help.html"
    }
  </script>

  <script>
    const blacks = document.querySelectorAll(".black");
    const whites = document.querySelectorAll(".white");
    const indexs = [...document.querySelector(".index").children];

    // 保存按下键的key
    const keySet = new Set();
    // 声音级别
    let index = 5;
    // 音频
    const audioMap = new Map();


    // 键盘按下时奏乐，键盘键起时音乐渐隐
    const sound = (num, type = "keydown") => {
      let key = [, "C", "D", "E", "F", "G", "A", "B"][num];
      if (!key) return;
      key = `${keySet.has("Space") ? '#' : ''}${key}${index - 1}`;
      const audio = audioMap.get(key);
      if (!audio) return;
      if (type == "keydown") {
        audio.play();
      } else if (type == "keyup") {
        audioMap.set(key, new Audio(audio.src));
        fadeSound(audio);
      }
      const item = keySet.has("Space") ? blacks[num - 1] : whites[num - 1];
      item.classList.toggle("active")
    }
    // 音乐渐隐
    const fadeSound = (audio) => {
      if (audio.currentTime >= audio.duration) return;
      const volume = audio.volume - 0.1;
      if (volume <= 0) return audio.volume = 0;;
      audio.volume = volume;
      setTimeout(fadeSound, 50, audio);
    }
    // 设置声音级别，切换时自动抬起按键
    const setIndex = (digit) => {
      if (index == digit) return;
      for (const key of [...keySet].filter(item => item.startsWith("Numpad"))) {
        keySet.delete(key);
        const num = key.replace("Numpad", "");
        sound(num, "keyup");
      }
      index = digit || 5;
      for (let i = 0; i < indexs.length; i++)
        index == i + 1 ? indexs[i].classList.add("active") : indexs[i].classList.remove("active");
    }
    // 设置空格键，空格键切换黑白键，切换时自动抬起按键
    const setSpace = () => {
      document.querySelector(".space").classList.toggle("active");
      for (const key of [...keySet].filter(item => item.startsWith("Numpad"))) {
        keySet.delete(key);
        const num = key.replace("Numpad", "");
        keySet.has("Space") ? keySet.delete("Space") : keySet.add("Space");
        sound(num, "keyup");
        keySet.has("Space") ? keySet.delete("Space") : keySet.add("Space");
      }
    }

    // 处理键盘事件
    const handleKeyEvent = ev => {
      let numpad; // 键盘右边小键盘的数字
      let digit; // 键盘头部一行键盘的数字
      let black; // 鼠标点击黑键的数字
      if (ev.code.startsWith("Numpad"))
        numpad = ev.code.replace("Numpad", "");
      else if (ev.code.startsWith("Digit"))
        digit = ev.code.replace("Digit", "");
      else if (ev.code.startsWith("Black"))
        black = ev.code.replace("Black", "");

      if (ev.type == "keydown") {
        if (keySet.has(ev.code)) return;
        keySet.add(ev.code);
        if (numpad) sound(numpad);
        else if (digit && digit >= 1 && digit <= 9) setIndex(digit);
        else if (black) {
          keySet.add("Space");
          sound(black);
          keySet.delete("Space");
        }

      } else if (ev.type == "keyup") {
        if (!keySet.has(ev.code)) return;
        keySet.delete(ev.code);
        if (numpad) sound(numpad, "keyup");
        else if (black) {
          keySet.add("Space");
          sound(black, "keyup");
          keySet.delete("Space");
        }
        // 自动回弹
        // else if (digit) {
        //   const findDigit = [...keySet].reverse().find(item => item.startsWith("Digit"))?.replace("Digit", "");
        //   setIndex(findDigit)
        // }
      }
      if (ev.code == "Space") setSpace();
    }


    // 初始化，移除加载动画，绑定键盘事件
    const init = () => {
      const loading = document.querySelector(".loading");
      loading.classList.add("hide");
      setTimeout(() => loading.classList.add("hided"), 1500);

      blacks.forEach((item, index) => {
        item.addEventListener("pointerdown", () => handleKeyEvent({ code: `Black${index + 1}`, type: 'keydown' }));
        item.addEventListener("pointerup", () => handleKeyEvent({ code: `Black${index + 1}`, type: 'keyup' }));
        item.addEventListener("pointerleave", () => handleKeyEvent({ code: `Black${index + 1}`, type: 'keyup' }));
      });

      whites.forEach((item, index) => {
        item.addEventListener("pointerdown", () => handleKeyEvent({ code: `Numpad${index + 1}`, type: 'keydown' }));
        item.addEventListener("pointerup", () => handleKeyEvent({ code: `Numpad${index + 1}`, type: 'keyup' }));
        item.addEventListener("pointerleave", () => handleKeyEvent({ code: `Numpad${index + 1}`, type: 'keyup' }));
      });

      indexs.forEach((item, index) => {
        item.addEventListener("pointerdown", () => handleKeyEvent({ code: `Digit${index + 1}`, type: 'keydown' }));
        item.addEventListener("pointerup", () => handleKeyEvent({ code: `Digit${index + 1}`, type: 'keyup' }));
        item.addEventListener("pointerleave", () => handleKeyEvent({ code: `Digit${index + 1}`, type: 'keyup' }));
      });

      window.addEventListener("keydown", handleKeyEvent);
      window.addEventListener("keyup", handleKeyEvent);
    }


    let count = 0;
    const createAudio = async (key) => {
      const path = encodeURIComponent(key.replace(/\d/, ""));
      const name = encodeURIComponent(key);
      let url = `./sound/${path}/${name}.mp3`;
      if (location.href.startsWith("http")) {
        await fetch(url).then(async data => {
          const blob = await data.blob();
          url = URL.createObjectURL(blob);
        }).catch(() => null);
      }
      const audio = new Audio(url);
      audioMap.set(key, audio);
      document.querySelector(".loading .text").textContent = ++count >= 88 ? `加载完成` : `正在加载音频文件 ${count} / 88`
    }
    // 加载音频文件
    const loadAudio = async () => {

      await createAudio("A0");
      await createAudio("#A0");
      await createAudio("B0");
      await createAudio("C8");
      for (const key of ["A", "B", "C", "D", "E", "F", "G"]) {
        for (const i of [1, 2, 3, 4, 5, 6, 7]) {
          await createAudio(`${key}${i}`);
          if (["A", "C", "D", "F", "G"].includes(key)) await createAudio(`#${key}${i}`);
        }
      }
    }

    window.addEventListener("load", async () => {
      await loadAudio();
      init();
    });

  </script>

</body>

</html>